/*
 * University of British Columbia
 * Department of Computer Science
 * CPSC317 - Internet Programming
 * Assignment 1
 * 
 * Author: 
 * January 2012
 * 
 * This code may not be used without written consent of the authors, except for 
 * current and future projects and assignments of the CPSC317 course at UBC.
 */

package ubc.cics515.xmpp.net;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import ubc.cics515.xmpp.exception.XMPPException;
import ubc.cics515.xmpp.model.Contact;
import ubc.cics515.xmpp.model.ContactStatus;
import ubc.cics515.xmpp.model.Message;
import ubc.cics515.xmpp.model.Session;

/**
 * This class describes the XMPP connection handler. A socket connection is
 * created when an instance of this handler is created, and methods are provided
 * for most common operations in XMPP.
 * 
 * This class will not in any case make a direct reference to any class or
 * method that represents a specific UI library.
 */
public class XMPPConnection {
	
	/**
	 * our variables
	 */
	boolean hasLOGIN;
	boolean hasPLAIN;
	

	/**
	 * Default TCP port for client-server communication in XMPP.
	 */
	public static final int XMPP_DEFAULT_PORT = 5222;

	/**
	 * Session object for communication between the network component and the
	 * chat model and UI.
	 */
	private Session session;

	/**
	 * Socket object associated to the communication between this client and the
	 * XMPP server.
	 */
	private Socket socket;

	/**
	 * XMPP reader helper, used to obtain XML nodes from the XMPP stream.
	 */
	// Variable xmppReader is unused in provided code, but you should use it in
	// your code. Once that is done, the @SuppressWarnings can be removed.
	@SuppressWarnings("unused")
	private XMPPStreamReader xmppReader;
	
	/**
	 * XMPP writer helper, used to write XML nodes to the XMPP stream.
	 */
	// Variable xmppWriter is unused in provided code, but you should use it in
	// your code. Once that is done, the @SuppressWarnings can be removed.
	@SuppressWarnings("unused")
	private XMPPStreamWriter xmppWriter;

	/**
	 * Creates a new instance of the connection handler. This constructor will
	 * creating the socket, initialise the reader and writer helpers, send
	 * initial tags, authenticate the user and bind to a resource.
	 * 
	 * @param jidUser
	 *            User part of the Jabber ID.
	 * @param jidDomain
	 *            Domain part of the Jabber ID.
	 * @param resource
	 *            Resource to bind once authenticated. If null or empty, a new
	 *            resource will be generated.
	 * @param password
	 *            Password for authentication.
	 * @param session
	 *            Instance of the session to communicate with other parts of the
	 *            system.
	 * @throws XMPPException
	 *             If there is an error establishing the connection, sending or
	 *             receiving necessary data, or while authenticating.
	 */
	public XMPPConnection(String jidUser, String jidDomain, String resource,
			String password, Session session) throws XMPPException {
		
		// initialize our variables
		this.hasLOGIN = false;
		this.hasPLAIN = false;

		this.session = session;

		initializeConnection(jidDomain);

		try {
			xmppReader = new XMPPStreamReader(socket.getInputStream());
			xmppWriter = new XMPPStreamWriter(socket.getOutputStream());
		} catch (XMPPException e) {
			throw e;
		} catch (Exception e) {
			throw new XMPPException("Could not obtain socket I/O channels ("
					+ e.getMessage() + ")", e);
		}

		initializeStreamAndFeatures(jidUser, jidDomain);

		login(jidUser, password);

		bindResource(resource);

		startListeningThread();
	}

	/**
	 * Initialises the connection with the specified domain. This method sets
	 * the socket field with an initialised socket.
	 * 
	 * @param domain
	 *            DNS name (or IP string) of the server to connect to.
	 * @throws XMPPException
	 *             If there is a problem connecting to the server.
	 */
	private void initializeConnection(String domain) throws XMPPException {

		/* YOUR CODE HERE 
		 * hints: for error, throw new XMPPEXception ("Doesn't", e)*/
		try {
			socket = new Socket(domain, XMPP_DEFAULT_PORT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new XMPPException ("Cannot create socket", e);
		}
	}

	/**
	 * Sends the initial data to establish an XMPP connection stream with the
	 * XMPP server. This method also retrieves the set of features from the
	 * server, saving it in a field for future use.
	 * 
	 * @param jidUser
	 *            User part of the Jabber ID.
	 * @param jidDomain
	 *            Domain part of the Jabber ID.
	 * @throws XMPPException
	 *             If there is a problem sending or receiving the data.
	 */
	private void initializeStreamAndFeatures(String jidUser, String jidDomain)
			throws XMPPException {

		/* YOUR CODE HERE 
		 * hints: -don't send </stream:>, cause it is used to close connection
		 * 		  - receive <streamfeature>
		 * 				 <mechanisms>
		 * 						<mechanism>Login</mechanism> have to check this mechanism
		 * 		  - only do one mechanism*/
		Element rootElement = xmppWriter.createRootElement("stream:stream");
		rootElement.setAttribute("from", jidUser+ "@" + jidDomain);
		rootElement.setAttribute("to",jidDomain);
		rootElement.setAttribute("version", "1.0");
		rootElement.setAttribute("xml:lang", "en");
		rootElement.setAttribute("xmlns", "jabber:client");
		rootElement.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
		xmppWriter.writeRootElementWithoutClosingTag();
		
		Element response = xmppReader.readSecondLevelElement();
		NodeList mechanisms = response.getElementsByTagName("mechanism");
		
		xmppWriter.debugElement(System.out, response);
		
		for (int i = 0; i <= mechanisms.getLength()-1; i++)
		{
			if ( mechanisms.item(i).getTextContent().equals("LOGIN") )
			{
				this.hasLOGIN = true;
			}
			if (mechanisms.item(i).getTextContent().equals("PLAIN"))
			{
				this.hasPLAIN = true;
			}
			
		}
		
		if ((!this.hasLOGIN) && (!this.hasPLAIN))
			throw new XMPPException ("Server does not support SASL mechanisms: LOGIN or PLAIN");
			
		
	}

	/**
	 * Attempts to authenticate the user name with the provided password at the
	 * connected server. This method will verify if the server supports the
	 * implemented authentication mechanism(s) and send the user and password
	 * based on the first mechanism it finds. In case authentication is not
	 * successful, this function will throw an XMPPException. This function also
	 * retrieves the new set of features available after authentication.
	 * 
	 * @param username
	 *            User name to use for authentication.
	 * @param password
	 *            Password to use for authentication.
	 * @throws XMPPException
	 *             If authentication is not successful, or if authentication
	 *             methods supported by the server are not implemented, or if
	 *             there was a problem sending authentication data.
	 */
	private void login(String username, String password) throws XMPPException {

		/* YOUR CODE HERE 
		 * hints: - <auth mechanism = LOGIN ```> some string(base-64 encoding) </auth>
		 * 		  - check success and failure (<success /> <failure/>)
		 * 		  - first send equal mark in 'some string', server reply challenge, then send user name and password, and then server 
		 *          reply success or failure or another challenge */
	}

	/**
	 * Binds the connection to a specific resource, or retrieves a
	 * server-generated resource if one is not provided. This function will wait
	 * until a resource is sent by the server.
	 * 
	 * @param resource
	 *            Name of the user-specified resource. If resource is null or
	 *            empty, retrieves a server-generated one.
	 * @throws XMPPException
	 *             If there is an error sending or receiving the data.
	 */
	private void bindResource(String resource) throws XMPPException {

		/* YOUR CODE HERE 
		 * hints: multiple login for the same account, ask the server for resource identification
		 *        -check resource before send resource, since the resource probably is already there
		 *        -send <iq
		 *        -you have to test this*/
	}

	/**
	 * Starts a thread that will keep listening for new messages asynchronously
	 * from the main thread.
	 */
	private void startListeningThread() {
		Thread listeningThread = new Thread(new Runnable() {

			@Override
			public void run() {
				listeningProcess();
			}
		});
		listeningThread.start();
	}

	/**
	 * Keeps listening for new XML elements in a loop until a closing tag is
	 * found or an exception happens. If an exception happens, calls
	 * <code>session.processReceivedException</code> and closes the connection.
	 * If the closing tag is found, sends a closing tag back and closes the
	 * connection as well. In both cases, the connection is closed at the
	 * session level, so that the model and UI can handle the connection being
	 * closed. For each received element, processes the element and handles it
	 * accordingly.
	 */
	private void listeningProcess() {

		/* YOUR CODE HERE */
	}

	/**
	 * Closes the connection. If the connection was already closed before this
	 * method is called nothing is done, otherwise sends all necessary closing
	 * data and waits for the server to send the closing data as well. Once this
	 * happens the socket connection is closed. This method does not throw any
	 * exception, choosing instead to ignore them. However, even if an exception
	 * happens while sending the final data, the socket connection will be
	 * closed.
	 */
	public synchronized void closeConnection() {

		/* YOUR CODE HERE */
	}

	/**
	 * Sends a request for the contact list. The result is not expected to be
	 * received in this function, but it should come in a message that will be
	 * handled by the listening process.
	 * 
	 * @throws XMPPException
	 *             If there was a problem sending the request.
	 */
	public void sendRequestForContactList() throws XMPPException {

		/* YOUR CODE HERE */
	}

	/**
	 * Sends an updated status information to the server, based on the status
	 * currently attributed to the session.
	 * 
	 * @throws XMPPException
	 *             If there was a problem sending the status.
	 */
	public void sendCurrentStatus() throws XMPPException {
		sendStatus(session.getCurrentStatus());
	}

	/**
	 * Sends a specific status information to the server.
	 * 
	 * @param status
	 *            Status to send to the server.
	 * @throws XMPPException
	 *             If there was a problem sending the status.
	 */
	private void sendStatus(ContactStatus status) throws XMPPException {

		/* YOUR CODE HERE */
	}

	/**
	 * Sends a request that a new contact be added to the list of contacts.
	 * Additionally, requests authorization from that contact to receive updates
	 * any time the contact changes its status. This function does not add the
	 * user to the local list of contacts, which happens at the listening
	 * process once the server sends an update to the list of contacts as a
	 * result of this request.
	 * 
	 * @param contact
	 *            Contact that should be requested.
	 * @throws XMPPException
	 *             If there is a problem sending the request.
	 */
	public void sendNewContactRequest(Contact contact) throws XMPPException {

		/* YOUR CODE HERE */
	}

	/**
	 * Sends a response message to a contact that requested authorization to
	 * receive updates when the local user changes its status.
	 * 
	 * @param jid
	 *            Jabber ID of the contact that requested authorization.
	 * @param accepted
	 *            <code>true</code> if the request was accepted by the user,
	 *            <code>false</code> otherwise.
	 * @throws XMPPException
	 *             If there was an error sending the response.
	 */
	public void respondContactRequest(String jid, boolean accepted)
			throws XMPPException {

		/* YOUR CODE HERE */
	}

	/**
	 * Request that the server remove a specific contact from the list of
	 * contacts. Additionally, requests that no further status updates be sent
	 * regarding that contact, as well as that no further status updates about
	 * the local user be sent to that contact. This function does not remove the
	 * user from the local list of contacts, which happens at the listening
	 * process once the server sends an update to the list of contacts as a
	 * result of this request.
	 * 
	 * @param contact
	 *            Contact to be removed from the list of contacts.
	 * @throws XMPPException
	 *             If there was an error sending the request.
	 */
	public void removeAndUnsubscribeContact(Contact contact)
			throws XMPPException {

		/* YOUR CODE HERE */
	}

	/**
	 * Send a chat message to a specific contact.
	 * 
	 * @param message
	 *            Message to be sent.
	 * @throws XMPPException
	 *             If there was a problem sending the message.
	 */
	public void sendMessage(Message message) throws XMPPException {

		/* YOUR CODE HERE */
	}
}
